﻿using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Cronos;

namespace ORS.OrderRobot
{
    public class DefaultBackgroundWorkerManager : IBackgroundWorkerManager
    {
        private readonly IServiceProvider _provider;
        private readonly ILogger<DefaultBackgroundWorkerManager> _logger;
        private readonly Dictionary<string, IBackgroundWorker> _workers = new();
        private readonly Dictionary<string, CancellationTokenSource> _tokens = new();

        public DefaultBackgroundWorkerManager(IServiceProvider provider, ILogger<DefaultBackgroundWorkerManager> logger)
        {
            _provider = provider;
            _logger = logger;
        }

        public void Register<T>() where T : IBackgroundWorker
        {
            var workerType = typeof(T);
            var name = workerType.Name;

            using var scope = _provider.CreateScope();
            var instance = (IBackgroundWorker)scope.ServiceProvider.GetRequiredService(workerType);
            _workers[name] = instance;
        }

        public Task StartAllAsync(CancellationToken cancellationToken = default)
            => Task.WhenAll(_workers.Keys.Select(name => StartAsync(name, cancellationToken)));

        public Task StopAllAsync(CancellationToken cancellationToken = default)
            => Task.WhenAll(_workers.Keys.Select(name => StopAsync(name, cancellationToken)));

        public async Task StartAsync(string workerName, CancellationToken cancellationToken = default)
        {
            if (!_workers.TryGetValue(workerName, out var worker))
            {
                _logger.LogWarning("Worker {Name} not found", workerName);
                return;
            }

            if (worker is ICronWorker cronWorker)
            {
                var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
                _tokens[workerName] = cts;

                var cron = await cronWorker.GetCronExpressionAsync();
                var schedule = CronExpression.Parse(cron);
                var tz = TimeZoneInfo.FindSystemTimeZoneById("Asia/Shanghai");

                _ = Task.Run(async () =>
                {
                    while (!cts.Token.IsCancellationRequested)
                    {
                        var next = schedule.GetNextOccurrence(DateTime.UtcNow, tz);
                        var delay = (next ?? DateTime.UtcNow.AddMinutes(1)) - DateTime.UtcNow;

                        await Task.Delay(delay, cts.Token);
                        await cronWorker.ExecuteAsync();
                    }
                }, cts.Token);
            }
            else
            {
                await worker.StartAsync(cancellationToken);
            }
        }

        public Task StopAsync(string workerName, CancellationToken cancellationToken = default)
        {
            if (_tokens.TryGetValue(workerName, out var cts))
            {
                cts.Cancel();
            }

            if (_workers.TryGetValue(workerName, out var worker))
            {
                return worker.StopAsync(cancellationToken);
            }

            return Task.CompletedTask;
        }
    }
}
